package cn.tedu.reflection;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/*本类用于测试反射*/
public class Test1Reflect {
    //1.创建程序的入口函数main（）---不用
    /*单元测试方法一：是java测试的最小范围，使用灵活，推荐程度：5颗星
      语法要求：@Test + void + 没有参数 + public
    * */
    /*单元测试一*/
    @Test
    public void getClazz() throws ClassNotFoundException {
        //方式1：
        Class<?> student1 = Class.forName("cn.tedu.reflection.Student");
        //方式2：直接类名.Class获取对应的字节码对象
        Class<?> student2 = Student.class;
        //方式3：通过类的对象来获取，注意，此处创建的是匿名对象，匿名对象没有名字
        Class<?> student3 = new Student().getClass();
        System.out.println(student1);//打印通过反射获取到的字节码对象
        System.out.println(student2.getName());//cn.tedu.reflection.Student 全路径名
        System.out.println(student3.getSimpleName());// Student 获取类名
        System.out.println(student3.getPackage().getName());// cn.tedu.reflection 获取包名
    }
    /*单元测试方法二：获取构造方法*/
    @Test
    public void getConstruct(){
        //1.
        Class<?> clazz = Student.class;
        //2.
        Constructor<?>[] cs = clazz.getConstructors();
        //3.使用增强for循环遍历存放所有构造函数的数组
        //for(1 2 : 3){}
        for( Constructor c : cs){

            System.out.println(c.getName());
            Class[] cp = c.getParameterTypes();
            System.out.println(Arrays.toString(cp));
        }
    }
    /*单元测试方法三：*/
    @Test
    public void getFunction() throws Exception {
        //获取字节码对象
        Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
        //获取指定类中的所有普通方法
        Method[] ms = clazz.getMethods();
        //遍历存放所有方法的数组
        for(Method m : ms){
            //通过当前遍历到的方法对象获取当前方法的名字
            System.out.println(m.getName());
            //通过当前遍历到的方法对象获取当前方法的参数类型
            Class<?>[] pt = m.getParameterTypes();
            System.out.println(Arrays.toString(pt));
        }
    }
    /*单元测试方法四：获取成员变量*/
    @Test
    public void getFields(){
        /*注意！！ 目前所有成员变量的修饰符必修是public才能获取到
        * 如果属性采用的是默认的修饰符，是反射不到的*/
        //1.获取Class字节码对象
        /* Class<?> 中的“？”是指泛型约束的通配符，类似于“*”  */
        Class<?>clazz = Student.class;
        //2.通过字节码对象获取所有的成员变量
        Field[] fs = clazz.getFields();
        //3.遍历数组，获取每个成员变量的信息
        for(Field f :fs){
            System.out.println(f.getName());
            System.out.println(f.getType().getName());
        }
    }
    /*单元测试五：通过单元测试来反射创建对象
    * 方式一：通过字节码对象直接调用newInstance()，触发无参构造来创建对象
    * 方式二：先获取指定的构造函数，再通过构造函数对象调用newInstance()，触发对应类型的构造函数来创建对象
    * */
    @Test
    public void makeObject() throws Exception {
        //1.获取字节码对象class
        Class<?> clazz = Student.class;
        //2.创建对象
        Object obj = clazz.newInstance();//触发的是无参构造
        System.out.println(obj);
        //3.通过全参构造创建对象
        //3.1通过制定的构造函数来创建对象
        /*注意参数类型的指定，是对应类型的字节码对象，不能直接光写类型*/
        Constructor<?>c = clazz.getConstructor(String.class,int.class);
        //3.2触发刚刚获取到的全参构造
        Object obj2 = c.newInstance("张三",18);/*此处是多态的向上造型*/
        System.out.println(obj2);
        /*查看对象具体的属性值，或者调用方法，需要把Object强转成指定的子类对象
        * 为什么要把Object转成子类类型呢？因为相要使用子类的属性或者是特有功能
        * 父类是无法使用子类的特有属性和功能的，上面的obj2就是Object类型
        * Object类中没有Student中的属性和特有功能
        * 这种把之前看作是父类类型的子类对象，重新转回成子类对象的现象，叫做向下造型
        * */
        Student s = (Student) obj2;
        System.out.println(s.name);
        System.out.println(s.age);
        s.study();

    }



}
